/** @file
  This is an implementation of the AcpiVariable platform field for ECP platform.

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>

SPDX-License-Identifier: BSD-2-Clause-Patent

==

typedef struct {
  EFI_PHYSICAL_ADDRESS  AcpiReservedMemoryBase;  <<===
  UINT32                AcpiReservedMemorySize;  <<===
  EFI_PHYSICAL_ADDRESS  S3ReservedLowMemoryBase;
  EFI_PHYSICAL_ADDRESS  AcpiBootScriptTable;
  EFI_PHYSICAL_ADDRESS  RuntimeScriptTableBase;
  EFI_PHYSICAL_ADDRESS  AcpiFacsTable;
  UINT64                SystemMemoryLength;      <<===
  ACPI_CPU_DATA_COMPATIBILITY         AcpiCpuData;
  EFI_PHYSICAL_ADDRESS  VideoOpromAddress;
  UINT32                VideoOpromSize;
  EFI_PHYSICAL_ADDRESS  S3DebugBufferAddress;
  EFI_PHYSICAL_ADDRESS  S3ResumeNvsEntryPoint;
} ACPI_VARIABLE_SET_COMPATIBILITY;

**/

#include <FrameworkDxe.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiLib.h>
#include <Protocol/FrameworkMpService.h>
#include <Protocol/VariableLock.h>
#include <Guid/AcpiVariableCompatibility.h>

GLOBAL_REMOVE_IF_UNREFERENCED
ACPI_VARIABLE_SET_COMPATIBILITY               *mAcpiVariableSetCompatibility = NULL;

/**
  Allocate memory below 4G memory address.

  This function allocates memory below 4G memory address.

  @param  MemoryType   Memory type of memory to allocate.
  @param  Size         Size of memory to allocate.

  @return Allocated address for output.

**/
VOID*
AllocateMemoryBelow4G (
  IN EFI_MEMORY_TYPE    MemoryType,
  IN UINTN              Size
  );

/**
  Hook point for AcpiVariableThunkPlatform for S3Ready.

**/
VOID
S3ReadyThunkPlatform (
  VOID
  )
{
  EFI_PHYSICAL_ADDRESS                          AcpiMemoryBase;
  UINT32                                        AcpiMemorySize;
  EFI_PEI_HOB_POINTERS                          Hob;
  UINT64                                        MemoryLength;

  DEBUG ((EFI_D_INFO, "S3ReadyThunkPlatform\n"));

  if (mAcpiVariableSetCompatibility == NULL) {
    return;
  }

  //
  // Allocate ACPI reserved memory under 4G
  //
  AcpiMemoryBase = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocateMemoryBelow4G (EfiReservedMemoryType, PcdGet32 (PcdS3AcpiReservedMemorySize));
  ASSERT (AcpiMemoryBase != 0);
  AcpiMemorySize = PcdGet32 (PcdS3AcpiReservedMemorySize);

  //
  // Calculate the system memory length by memory hobs
  //
  MemoryLength  = 0x100000;
  Hob.Raw = GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR);
  ASSERT (Hob.Raw != NULL);
  while ((Hob.Raw != NULL) && (!END_OF_HOB_LIST (Hob))) {
    if (Hob.ResourceDescriptor->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) {
      //
      // Skip the memory region below 1MB
      //
      if (Hob.ResourceDescriptor->PhysicalStart >= 0x100000) {
        MemoryLength += Hob.ResourceDescriptor->ResourceLength;
      }
    }
    Hob.Raw = GET_NEXT_HOB (Hob);
    Hob.Raw = GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, Hob.Raw);
  }

  mAcpiVariableSetCompatibility->AcpiReservedMemoryBase = AcpiMemoryBase;
  mAcpiVariableSetCompatibility->AcpiReservedMemorySize = AcpiMemorySize;
  mAcpiVariableSetCompatibility->SystemMemoryLength     = MemoryLength;

  DEBUG((EFI_D_INFO, "AcpiVariableThunkPlatform: AcpiMemoryBase is 0x%8x\n", mAcpiVariableSetCompatibility->AcpiReservedMemoryBase));
  DEBUG((EFI_D_INFO, "AcpiVariableThunkPlatform: AcpiMemorySize is 0x%8x\n", mAcpiVariableSetCompatibility->AcpiReservedMemorySize));
  DEBUG((EFI_D_INFO, "AcpiVariableThunkPlatform: SystemMemoryLength is 0x%8x\n", mAcpiVariableSetCompatibility->SystemMemoryLength));

  return ;
}

/**
  Register callback function upon VariableLockProtocol
  to lock ACPI_GLOBAL_VARIABLE variable to avoid malicious code to update it.

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the notification function's context.
**/
VOID
EFIAPI
VariableLockAcpiGlobalVariable (
  IN  EFI_EVENT                             Event,
  IN  VOID                                  *Context
  )
{
  EFI_STATUS                    Status;
  EDKII_VARIABLE_LOCK_PROTOCOL  *VariableLock;
  //
  // Mark ACPI_GLOBAL_VARIABLE variable to read-only if the Variable Lock protocol exists
  //
  Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **) &VariableLock);
  if (!EFI_ERROR (Status)) {
    Status = VariableLock->RequestToLock (VariableLock, ACPI_GLOBAL_VARIABLE, &gEfiAcpiVariableCompatiblityGuid);
    ASSERT_EFI_ERROR (Status);
  }
}

/**
  Hook point for AcpiVariableThunkPlatform for InstallAcpiS3Save.
**/
VOID
InstallAcpiS3SaveThunk (
  VOID
  )
{
  EFI_STATUS                           Status;
  FRAMEWORK_EFI_MP_SERVICES_PROTOCOL   *FrameworkMpService;
  UINTN                                VarSize;
  VOID                                 *Registration;

  Status = gBS->LocateProtocol (
                  &gFrameworkEfiMpServiceProtocolGuid,
                  NULL,
                  (VOID**) &FrameworkMpService
                  );
  if (!EFI_ERROR (Status)) {
    //
    // On ECP platform, if framework CPU drivers are in use, The compatible version of ACPI variable set
    // should be produced by CPU driver.
    //
    VarSize = sizeof (mAcpiVariableSetCompatibility);
    Status = gRT->GetVariable (
                    ACPI_GLOBAL_VARIABLE,
                    &gEfiAcpiVariableCompatiblityGuid,
                    NULL,
                    &VarSize,
                    &mAcpiVariableSetCompatibility
                    );
    if (EFI_ERROR (Status) || (VarSize != sizeof (mAcpiVariableSetCompatibility))) {
      DEBUG ((EFI_D_ERROR, "FATAL ERROR: AcpiVariableSetCompatibility was not saved by CPU driver correctly. OS S3 may fail!\n"));
      mAcpiVariableSetCompatibility = NULL;
    }
  } else {
    //
    // Allocate/initialize the compatible version of Acpi Variable Set since Framework chipset/platform
    // driver need this variable. ACPI_GLOBAL_VARIABLE variable is not used in runtime phase,
    // so RT attribute is not needed for it.
    //
    mAcpiVariableSetCompatibility = AllocateMemoryBelow4G (EfiACPIMemoryNVS, sizeof(ACPI_VARIABLE_SET_COMPATIBILITY));
    Status = gRT->SetVariable (
                    ACPI_GLOBAL_VARIABLE,
                    &gEfiAcpiVariableCompatiblityGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                    sizeof(mAcpiVariableSetCompatibility),
                    &mAcpiVariableSetCompatibility
                    );
    if (!EFI_ERROR (Status)) {
      //
      // Register callback function upon VariableLockProtocol
      // to lock ACPI_GLOBAL_VARIABLE variable to avoid malicious code to update it.
      //
      EfiCreateProtocolNotifyEvent (
        &gEdkiiVariableLockProtocolGuid,
        TPL_CALLBACK,
        VariableLockAcpiGlobalVariable,
        NULL,
        &Registration
        );
    } else {
      DEBUG ((EFI_D_ERROR, "FATAL ERROR: AcpiVariableSetCompatibility cannot be saved: %r. OS S3 may fail!\n", Status));
      gBS->FreePages (
             (EFI_PHYSICAL_ADDRESS) (UINTN) mAcpiVariableSetCompatibility,
             EFI_SIZE_TO_PAGES (sizeof (ACPI_VARIABLE_SET_COMPATIBILITY))
             );
      mAcpiVariableSetCompatibility = NULL;
    }
  }

  DEBUG((EFI_D_INFO, "AcpiVariableSetCompatibility is 0x%8x\n", mAcpiVariableSetCompatibility));
}
